<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content="C++中的内存泄漏"><meta name="keywords" content="C++"><meta name="author" content="LiYang"><meta name="copyright" content="LiYang"><title>C++中的内存泄漏 | 一条鲤鱼</title><link rel="shortcut icon" href="/melody-favicon.ico"><link rel="stylesheet" href="/css/index.css?version=1.9.0"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.9.0"><meta name="format-detection" content="telephone=no"><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="https://cdn.jsdelivr.net"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><script>var GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  copy: {
    success: 'Copy successfully',
    error: 'Copy error',
    noSupport: 'The browser does not support'
  },
  hexoVersion: '5.4.0'
} </script><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="一条鲤鱼" type="application/atom+xml">
</head><body><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="true"><div class="toggle-sidebar-info text-center"><span data-toggle="Toggle article">Toggle site</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">Catalog</div><div class="sidebar-toc__progress"><span class="progress-notice">You've read</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#C-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E5%8E%9F%E5%9B%A0%E3%80%81%E9%81%BF%E5%85%8D%E5%8F%8A%E5%AE%9A%E4%BD%8D"><span class="toc-number">1.</span> <span class="toc-text">C++内存泄漏原因、避免及定位</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%83%8C%E6%99%AF"><span class="toc-number">1.1.</span> <span class="toc-text">背景</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A6%82%E5%BF%B5"><span class="toc-number">1.2.</span> <span class="toc-text">概念</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%88%86%E7%B1%BB"><span class="toc-number">1.3.</span> <span class="toc-text">分类</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A0%B9%E6%BA%90"><span class="toc-number">1.4.</span> <span class="toc-text">根源</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#C-11%E5%BC%95%E5%85%A5%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E6%9D%A5%E9%81%BF%E5%85%8D%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F"><span class="toc-number">1.4.1.</span> <span class="toc-text">C++11引入智能指针来避免内存泄漏</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80"><span class="toc-number">1.4.2.</span> <span class="toc-text">进程的内存布局</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A0%88"><span class="toc-number">1.4.3.</span> <span class="toc-text">栈</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%88%E7%9A%84%E8%AE%BE%E7%BD%AE"><span class="toc-number">1.4.3.1.</span> <span class="toc-text">栈的设置</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%88%E7%9A%84%E5%88%86%E9%85%8D%E6%96%B9%E5%BC%8F"><span class="toc-number">1.4.3.2.</span> <span class="toc-text">栈的分配方式</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%88%E5%86%85%E5%AD%98%E7%9A%84%E7%89%B9%E7%82%B9"><span class="toc-number">1.4.3.3.</span> <span class="toc-text">栈内存的特点</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A0%86"><span class="toc-number">1.4.4.</span> <span class="toc-text">堆</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A0%86%E7%9A%84%E5%88%86%E9%85%8D%E6%96%B9%E5%BC%8F"><span class="toc-number">1.4.4.1.</span> <span class="toc-text">堆的分配方式</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A0%86%E5%86%85%E5%AD%98%E7%9A%84%E7%89%B9%E7%82%B9"><span class="toc-number">1.4.4.2.</span> <span class="toc-text">堆内存的特点</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A0%86%E5%92%8C%E6%A0%88%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="toc-number">1.4.5.</span> <span class="toc-text">堆和栈的区别</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E7%9A%84%E4%BA%A7%E7%94%9F%E6%96%B9%E5%BC%8F"><span class="toc-number">1.5.</span> <span class="toc-text">内存泄漏的产生方式</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%B8%B8%E5%8F%91%E6%80%A7%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F"><span class="toc-number">1.5.0.1.</span> <span class="toc-text">常发性内存泄漏</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%81%B6%E5%8F%91%E6%80%A7%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F"><span class="toc-number">1.5.0.2.</span> <span class="toc-text">偶发性内存泄漏</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%80%E6%AC%A1%E6%80%A7%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F"><span class="toc-number">1.5.0.3.</span> <span class="toc-text">一次性内存泄漏</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%9A%90%E5%BC%8F%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F"><span class="toc-number">1.5.0.4.</span> <span class="toc-text">隐式内存泄漏</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E7%9A%84%E5%88%86%E7%B1%BB"><span class="toc-number">1.6.</span> <span class="toc-text">内存泄漏的分类</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9C%AA%E9%87%8A%E6%94%BE"><span class="toc-number">1.6.1.</span> <span class="toc-text">未释放</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9C%AA%E5%88%86%E9%85%8D"><span class="toc-number">1.6.2.</span> <span class="toc-text">未分配</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#new-%E5%92%8C-free"><span class="toc-number">1.6.2.1.</span> <span class="toc-text">new 和 free</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#new-%E5%92%8C-delete"><span class="toc-number">1.6.2.2.</span> <span class="toc-text">new[] 和 delete</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%99%9A%E6%9E%90%E6%9E%84"><span class="toc-number">1.6.2.3.</span> <span class="toc-text">虚析构</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8"><span class="toc-number">1.6.2.4.</span> <span class="toc-text">循环引用</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%81%BF%E5%85%8D%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F"><span class="toc-number">1.7.</span> <span class="toc-text">避免内存泄漏</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%81%BF%E5%85%8D%E5%9C%A8%E5%A0%86%E4%B8%8A%E5%88%86%E9%85%8D"><span class="toc-number">1.7.0.1.</span> <span class="toc-text">避免在堆上分配</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%89%8B%E5%8A%A8%E9%87%8A%E6%94%BE"><span class="toc-number">1.7.0.2.</span> <span class="toc-text">手动释放</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%81%BF%E5%85%8D%E4%BD%BF%E7%94%A8%E8%A3%B8%E6%8C%87%E9%92%88"><span class="toc-number">1.7.0.3.</span> <span class="toc-text">避免使用裸指针</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8STL%E4%B8%AD%E6%88%96%E8%80%85%E8%87%AA%E5%B7%B1%E5%AE%9E%E7%8E%B0%E5%AF%B9%E8%B1%A1"><span class="toc-number">1.7.0.4.</span> <span class="toc-text">使用STL中或者自己实现对象</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88"><span class="toc-number">1.7.0.5.</span> <span class="toc-text">智能指针</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#RAII"><span class="toc-number">1.7.0.6.</span> <span class="toc-text">RAII</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E7%9A%84%E5%AE%9A%E4%BD%8D"><span class="toc-number">1.8.</span> <span class="toc-text">内存泄漏的定位</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%97%A5%E5%BF%97"><span class="toc-number">1.8.0.1.</span> <span class="toc-text">日志</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%BB%9F%E8%AE%A1"><span class="toc-number">1.8.0.2.</span> <span class="toc-text">统计</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%B7%A5%E5%85%B7"><span class="toc-number">1.8.0.3.</span> <span class="toc-text">工具</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-number">1.9.</span> <span class="toc-text">总结</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%82%E8%80%83"><span class="toc-number">1.9.1.</span> <span class="toc-text">参考</span></a></li></ol></li></ol></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="/img/avatar.png"></div><div class="author-info__name text-center">LiYang</div><div class="author-info__description text-center"></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">Articles</span><span class="pull-right">13</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">Tags</span><span class="pull-right">6</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">Categories</span><span class="pull-right">7</span></a></div></div></div><div id="content-outer"><div class="no-bg" id="top-container"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">一条鲤鱼</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus">   <a class="site-page" href="/">Home</a><a class="site-page" href="/archives">Archives</a><a class="site-page" href="/tags">Tags</a><a class="site-page" href="/categories">Categories</a></span><span class="pull-right"></span></div><div id="post-info"><div id="post-title">C++中的内存泄漏</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2022-05-13</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</a><i class="fa fa-angle-right" aria-hidden="true"></i><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/C/">C++</a></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><h1 id="C-内存泄漏原因、避免及定位"><a href="#C-内存泄漏原因、避免及定位" class="headerlink" title="C++内存泄漏原因、避免及定位"></a>C++内存泄漏原因、避免及定位</h1><p>对于C/C++开发人员来说，内存泄漏是最容易遇到的问题之一，众所周知，这是由C/C++语言的特性引起的。</p>
<p>C/C++语言与其他语言不同，需要开发者去申请和释放内存，即需要开发者去管理内存，如果内存使用不当，就容易造成<strong>段错误（segment fault）</strong>或者<strong>内存泄漏（memory leak）</strong>。</p>
<p>本文将针对C++项目中经常遇到的导致内存泄漏的原因，如何避免和定位内存泄漏进行分析。</p>
<p>主要内容如下：</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/C++内存泄漏.png" alt="C++内存泄漏" style="zoom:50%;">

<hr>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>C/C++语言中，内存的分配与回收都是由开发人员在编写代码时主动完成的，这样带来的<strong>好处</strong>是内存管理的开销较小，程序拥有更高的执行效率；<strong>弊端</strong>是依赖于开发者的水平，随着代码规模的扩大，极容易遗漏释放内存的步骤，或者一些不规范的编程可能会使程序具有安全隐患。</p>
<p>如果对内存管理不当，可能导致程序中存在内存缺陷，甚至会在运行时产生内存故障错误。内存泄漏是各类缺陷中十分棘手的一种，对系统的稳定运行威胁较大。</p>
<blockquote>
<p><strong>定义：当动态分配的内存在程序结束之前没有被回收时，则发生了内存泄漏。</strong></p>
</blockquote>
<p>由于系统软件，如操作系统、编译器、开发环境等都是由C/C++语言实现的，不可避免地存在内存泄漏缺陷，特别是一些在服务器上长期运行的软件，若存在内存泄漏则会造成严重后果，例如<strong>性能下降、程序终止、系统崩溃、无法提供服务</strong>。</p>
<hr>
<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><ul>
<li><strong>内存泄漏（Memory Leak）是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放，造成系统内存的浪费，导致程序运行速度减慢甚至系统崩溃等严重后果。</strong></li>
<li>当我们在程序中对原生指针(raw pointer)使用<code>new</code>操作符或者<code>free</code>函数的时候，实际上是在堆上为其分配内存，这个内存指的是RAM，而不是硬盘等永久存储。持续申请而不释放(或者少量释放)内存的应用程序，最终因内存耗尽导致<strong>内存溢出OOM（Out Of Memory）</strong>。</li>
</ul>
<blockquote>
<p><strong>定义：内存溢出：(Out Of Memory，简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多，最终使得程序运行要用到的内存大于能提供的最大内存。此时程序就运行不了，系统会提示内存溢出，有时候会自动关闭软件，重启电脑或者软件后释放掉一部分内存又可以正常运行该软件，而由系统配置、数据流、用户代码等原因而导致的内存溢出错误，即使用户重新执行任务依然无法避免。</strong></p>
</blockquote>
<h2 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h2><p>内存泄漏分为以下两类：</p>
<ul>
<li><strong>堆内存泄漏</strong>：我们经常说的内存泄漏就是堆内存泄漏，在堆上申请了资源，在结束使用的时候，没有释放归还给OS，从而导致该块内存永远不会被再次使用。</li>
<li><strong>资源泄漏</strong>：通常指的是系统资源，比如socket，文件描述符等，因为这些在系统中都是有限制的，如果创建了而不归还，久而久之，就会耗尽资源，导致其他程序不可用。</li>
</ul>
<p>本文主要分析堆内存泄漏，所以后面的内存泄漏均指的是<strong>堆内存泄漏</strong>。</p>
<hr>
<h2 id="根源"><a href="#根源" class="headerlink" title="根源"></a>根源</h2><h3 id="C-11引入智能指针来避免内存泄漏"><a href="#C-11引入智能指针来避免内存泄漏" class="headerlink" title="C++11引入智能指针来避免内存泄漏"></a>C++11引入智能指针来避免内存泄漏</h3><ul>
<li>内存泄漏，主要指的是在堆(heap)上申请的动态内存泄漏，或者说是指针指向的内存块忘了被释放，导致该块内存不能再被申请重新使用。</li>
<li>C++11引入了智能指针（unique_ptr、shared_ptr、weak_ptr）来对原生指针进行了一次封装，采用引用计数的方式来自动对不再使用的内存进行释放，从而使得程序员不用再显式调用delete释放内存了。但是内存泄漏的问题还是不能得到完全避免，最重要的一个原因是你不能保证组内其他人不使用原生指针，更不能保证合作部门不使用指针。</li>
<li>那么为什么C/C++中会存在指针呢？这就得从进程的内存布局说起。</li>
</ul>
<hr>
<h3 id="进程的内存布局"><a href="#进程的内存布局" class="headerlink" title="进程的内存布局"></a>进程的内存布局</h3><p>我们来看一下进程虚拟内存空间的布局：</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/640.png" alt="640" style="zoom:50%;">

<p>上图为32位进程的内存布局，从上图中主要包含以下几个块：</p>
<ul>
<li><p><strong>内核空间</strong>：供内核使用，存放的是内核代码和数据，负责进程的内存管理、进程管理、设备驱动管理和文件管理。</p>
</li>
<li><p><strong>stack</strong>：这就是我们经常所说的栈，用来存储自动变量(automatic variable)。</p>
</li>
<li><p><strong>mmap</strong>: 也成为内存映射，用来在进程虚拟内存地址空间中分配地址空间，创建和物理内存的映射关系。</p>
</li>
<li><p><strong>heap</strong>:就是我们常说的堆，动态内存的分配都是在堆上。</p>
</li>
<li><p><strong>bss</strong>:包含所有未初始化的全局和静态变量，此段中的所有变量都由0或者空指针初始化，程序加载器在加载程序时为BSS段分配内存。</p>
</li>
<li><p><strong>data</strong>:初始化的数据块：</p>
</li>
<li><ul>
<li>包含显式初始化的全局变量和静态变量</li>
<li>此段的大小由程序源代码中值的大小决定，在运行时不会更改</li>
<li>它具有读写权限，因此可以在运行时更改此段的变量值</li>
<li>该段可进一步分为初始化只读区和初始化读写区</li>
</ul>
</li>
<li><p><strong>text</strong>：也称为文本段</p>
</li>
<li><ul>
<li>该段包含已编译程序的二进制文件。</li>
<li>该段是一个只读段，用于防止程序被意外修改</li>
<li>该段是可共享的，因此对于文本编辑器等频繁执行的程序，内存中只需要一个副本</li>
</ul>
</li>
</ul>
<p>由于本文主要讲内存分配相关，所以下面的内容仅涉及到**栈(stack)<strong>和</strong>堆(heap)**。</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/2.png" alt="2" style="zoom:50%;">

<hr>
<h3 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h3><p>栈一块<strong>连续</strong>的内存块，栈上的内存分配就是在这一块连续内存块上进行操作的。<strong>编译器在编译的时候，就已经知道要分配的内存大小</strong>，当调用函数时候，其内部的遍历都会在栈上分配内存；当结束函数调用时候，内部变量就会被释放，进而将内存归还给栈。</p>
<p>比如下面的程序：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Object</span> &#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Object</span>() = <span class="keyword">default</span>;</span><br><span class="line">    <span class="comment">// ....</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Object obj;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述代码中，obj就是在栈上进行分配，当出了fun作用域的时候，会自动调用Object的析构函数对其进行释放。</p>
<ul>
<li>前面有提到，局部变量会在作用域（如函数作用域、块作用域等）结束后析构、释放内存。因为分配和释放的次序是刚好完全相反的，所以可用到堆栈<strong>先进后出（first-in-last-out, FILO）</strong>的特性，而 C++ 语言的实现一般也会使用到<strong>调用堆栈（call stack）</strong>来分配局部变量（但非标准的要求）。</li>
<li>因为栈上内存分配和释放，是一个进栈和出栈的过程(对于编译器只是一个指令)，所以<strong>相比于堆上的内存分配，栈要快的多。</strong></li>
<li>虽然栈的访问速度要快于堆，<strong>每个线程都有一个自己的栈，栈上的对象是不能跨线程访问的</strong>，这就决定了栈空间大小是有限制的，如果栈空间过大，那么在大型程序中几十乃至上百个线程，光栈空间就消耗了RAM，这就导致heap的可用空间变小，影响程序正常运行。</li>
</ul>
<hr>
<h4 id="栈的设置"><a href="#栈的设置" class="headerlink" title="栈的设置"></a>栈的设置</h4><p>在Linux系统上，可用通过如下命令来查看栈大小（单位为KB）：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ulimit -s</span><br><span class="line">10240</span><br></pre></td></tr></table></figure>

<p>可以通过shell命令修改栈大小（临时修改）：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ulimit -s 102400</span><br></pre></td></tr></table></figure>

<p>也可以修改<code>/etc/security/limits.conf</code>的内容，来永久修改。</p>
<hr>
<h4 id="栈的分配方式"><a href="#栈的分配方式" class="headerlink" title="栈的分配方式"></a>栈的分配方式</h4><p><strong>静态分配</strong></p>
<p>静态分配由编译器完成，例如局部变量以及函数参数等，都在编译期就分配好了。如下：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">int</span> a[<span class="number">10</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码中，a占<code>10 * sizeof(int)</code>个字节，在<strong>编译的时候直接计算好</strong>了，运行的时候，直接进栈出栈。</p>
<p><strong>动态分配</strong></p>
<p>可能很多人认为只有堆上才会存在动态分配，在栈上只可能是静态分配。其实，这个观点是错的，<strong>栈上也支持动态分配</strong>（敲黑板，划重点！！），该<strong>动态分配由alloca()函数进行分配</strong>。栈的动态分配和堆是不同的，<strong>通过alloca()函数分配的内存由编译器进行释放，无需手动操作。</strong></p>
<hr>
<h4 id="栈内存的特点"><a href="#栈内存的特点" class="headerlink" title="栈内存的特点"></a>栈内存的特点</h4><ul>
<li><strong>分配速度快</strong>：分配大小由编译器在编译期完成</li>
<li><strong>不会产生内存碎片</strong>：栈内存分配是连续的，以FILO的方式进栈和出栈</li>
<li><strong>大小受限</strong>：栈的大小依赖于操作系统</li>
<li><strong>访问受限</strong>：只能在当前函数或者作用域内进行访问</li>
</ul>
<hr>
<h3 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h3><p>堆（heap）是一种内存管理方式。内存管理对操作系统来说是一件非常复杂的事情，因为首先内存容量很大，其次就是内存需求在时间和大小块上没有规律（操作系统上运行着几十甚至几百个进程，这些进程可能随时都会申请或者是释放内存，并且申请和释放的内存块大小是随意的）。</p>
<p>堆这种内存管理方式的特点就是<strong>自由（随时申请、随时释放、大小块随意）</strong>。堆内存是操作系统划归给<strong>堆管理器</strong>（操作系统中的一段代码，属于操作系统的内存管理单元）来管理的，<strong>堆管理器提供了对应的接口_sbrk、_mmap等</strong>，只是该接口往往由<strong>运行时库(Linux为glibc)<strong>进行调用，即也可以说由运行时库进行堆内存管理，</strong>运行时库提供了malloc/free函数由开发人员调用</strong>，进而使用堆内存。</p>
<hr>
<h4 id="堆的分配方式"><a href="#堆的分配方式" class="headerlink" title="堆的分配方式"></a>堆的分配方式</h4><p>正如我们所理解的那样，由于是在运行期进行内存分配，<strong>分配的大小也在运行期才会知道</strong>，所以<strong>堆只支持动态分配</strong>，内存申请和释放的行为由开发者自行操作，这就<strong>很容易造成我们说的内存泄漏</strong>。</p>
<hr>
<h4 id="堆内存的特点"><a href="#堆内存的特点" class="headerlink" title="堆内存的特点"></a>堆内存的特点</h4><ul>
<li>变量可以在进程范围内访问，即进程内的所有线程都可以访问该变量</li>
<li>没有内存大小限制，这个其实是相对的，只是相对于栈大小来说没有限制，其实最终还是受限于RAM</li>
<li>相对栈来说访问比较慢</li>
<li>内存碎片</li>
<li>由开发者管理内存，即内存的申请和释放都由开发人员来操作</li>
</ul>
<h3 id="堆和栈的区别"><a href="#堆和栈的区别" class="headerlink" title="堆和栈的区别"></a>堆和栈的区别</h3><p><strong>对于栈来讲，是由编译器自动管理，无需我们手工控制；</strong></p>
<p><strong>对于堆来说，释放工作由程序员控制，容易产生memory leak。</strong></p>
<p>以下是堆和栈的主要区别：</p>
<ul>
<li><p><strong>空间大小不同</strong></p>
<ul>
<li>一般来讲在 32 位系统下，堆内存可以达到3G的空间，从这个角度来看堆内存几乎是没有什么限制的。</li>
<li>对于栈来讲，一般都是有一定的空间大小的，一般依赖于操作系统(也可以人工设置)</li>
</ul>
</li>
<li><p><strong>能否产生碎片不同</strong></p>
<ul>
<li>对于堆来讲，频繁的内存分配和释放势必会造成内存空间的不连续，从而造成大量的碎片，使程序效率降低。</li>
<li>对于栈来讲，内存都是连续的，申请和释放都是指令移动，类似于数据结构中的进栈和出栈</li>
</ul>
</li>
<li><p><strong>增长方向不同</strong></p>
<ul>
<li>对于堆来讲，生长方向是向上的，也就是向着内存地址增加的方向</li>
<li>对于栈来讲，它的生长方向是向下的，是向着内存地址减小的方向增长</li>
</ul>
</li>
<li><p><strong>分配方式不同</strong></p>
<ul>
<li>堆都是动态分配的，比如我们常见的malloc/new；而栈则有静态分配和动态分配两种。</li>
<li>静态分配是编译器完成的，比如局部变量的分配，而栈的动态分配则通过alloca()函数完成</li>
<li>二者动态分配是不同的，栈的动态分配的内存由编译器进行释放，而堆上的动态分配的内存则必须由开发人自行释放</li>
</ul>
</li>
<li><p><strong>分配效率不同</strong></p>
<ul>
<li>栈有操作系统分配专门的寄存器存放栈的地址，压栈出栈都有专门的指令执行，这就决定了栈的效率比较高</li>
<li>堆内存的申请和释放专门有运行时库提供的函数，里面涉及复杂的逻辑，申请和释放效率低于栈</li>
</ul>
</li>
</ul>
<p>讲到这里，栈和堆的基本特性以及各自的优缺点、使用场景已经分析完成，在这里给开发者一个建议，能使用栈的时候，就尽量使用栈，一方面是因为效率高于堆，另一方面内存的申请和释放由编译器完成，这样就避免了很多问题。</p>
<hr>
<p><strong>那为什么会有堆这个概念呢？</strong></p>
<p>既然栈的分配效率高，而且自动释放，但是空间有限，那么将栈空间扩展不就好了吗？</p>
<p>其实不然，<strong>虽然栈效率比较高，且不存在内存泄漏、内存碎片等，但是由于其本身的局限性(不能多线程、大小受限)，所以在很多时候，还是需要在堆上进行内存。</strong></p>
<p>我们来看一段代码：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">int</span> a;</span><br><span class="line">  <span class="keyword">int</span> *p;</span><br><span class="line">  p = (<span class="keyword">int</span> *)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">int</span>));</span><br><span class="line">  <span class="built_in">free</span>(p);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码很简单，有两个变量a和p，类型分别为int和int *，其中，a和p存储在栈上，p的值为在堆上的某块地址(在上述代码中，p的<strong>值</strong>为0x1c66010)，上述代码布局如下图所示：</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/640-16524049915771.png" alt="640-16524049915771" style="zoom:50%;">

<hr>
<h2 id="内存泄漏的产生方式"><a href="#内存泄漏的产生方式" class="headerlink" title="内存泄漏的产生方式"></a>内存泄漏的产生方式</h2><p>以产生的方式来分类，内存泄漏可以分为四类:</p>
<ul>
<li>常发性内存泄漏</li>
<li>偶发性内存泄漏</li>
<li>一次性内存泄漏</li>
<li>隐式内存泄漏</li>
</ul>
<hr>
<h4 id="常发性内存泄漏"><a href="#常发性内存泄漏" class="headerlink" title="常发性内存泄漏"></a><strong>常发性内存泄漏</strong></h4><p>产生内存泄漏的代码或者函数会被多次执行到，在每次执行的时候，都会产生内存泄漏。</p>
<hr>
<h4 id="偶发性内存泄漏"><a href="#偶发性内存泄漏" class="headerlink" title="偶发性内存泄漏"></a><strong>偶发性内存泄漏</strong></h4><p>与常发性内存泄漏不同的是，偶发性内存泄漏函数只在特定的场景下才会被执行。</p>
<p>偶发性内存泄漏常常是当程序运行中满足了某个不常出发的条件，调用了某个函数（或执行了某块代码），而这块代码中，存在内存泄漏的问题而导致的，也就是说偶发性内存泄漏不会每次执行都产生，往往需要测试人员及时发现。</p>
<hr>
<h4 id="一次性内存泄漏"><a href="#一次性内存泄漏" class="headerlink" title="一次性内存泄漏"></a>一次性内存泄漏</h4><p>这种内存泄漏在程序的生命周期内只会泄漏一次，或者说<strong>造成泄漏的代码只会被执行一次。</strong></p>
<p>有的时候，这种可能不算内存泄漏，或者说设计如此。比如如下服务程序：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">auto</span> *service = <span class="keyword">new</span> Service;</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  service-&gt;<span class="built_in">Run</span>();<span class="comment">// 服务启动</span></span><br><span class="line">  service-&gt;<span class="built_in">Loop</span>(); <span class="comment">// 可以理解为一个sleep，目的是使得程序不退出</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这种严格意义上，并不算内存泄漏，因为程序是这么设计的，即使程序异常退出，那么整个服务进程也就退出了，当然，在Loop()后面加个delete更好。</p>
<hr>
<h4 id="隐式内存泄漏"><a href="#隐式内存泄漏" class="headerlink" title="隐式内存泄漏"></a>隐式内存泄漏</h4><p><strong>程序在运行过程中不停的分配内存，但是直到结束的时候才释放内存。</strong>严格的说这里并没有发生内存泄漏，因为最终程序释放了所有申请的内存。但是对于一个服务器程序，需要运行几天，几周甚至几个月，不及时释放内存也可能导致最终耗尽系统的所有内存。所以，我们称这类内存泄漏为隐式内存泄漏。</p>
<p>比较常见的隐式内存泄漏有以下三种：</p>
<ul>
<li><strong>内存碎片</strong>：可以看下一篇文章《深入理解glibc内存管理精髓》，程序跑了几天之后，进程就因为OOM导致了退出，就是因为内存碎片导致剩下的内存不能被重新分配导致</li>
<li><strong>即使我们调用了free/delete，运行时库不一定会将内存归还OS</strong>，具体《深入理解glibc内存管理精髓》</li>
<li>用过STL的知道，<strong>STL</strong>内部有一个<strong>自己的allocator</strong>，我们可以当做一个<strong>memory poll</strong>，当调用vector.clear()时候，内存并不会归还OS，而是放回allocator，其内部根据一定的策略，在特定的时候将内存归还OS，跟glibc原理很像。</li>
</ul>
<hr>
<h2 id="内存泄漏的分类"><a href="#内存泄漏的分类" class="headerlink" title="内存泄漏的分类"></a>内存泄漏的分类</h2><p>内存泄漏产生的原因有很多，明确产生原因是我们解决内存泄漏问题的关键。</p>
<h3 id="未释放"><a href="#未释放" class="headerlink" title="未释放"></a>未释放</h3><p>最常见的内存泄漏就是开辟了堆内存空间，但是忘记释放，比如以下的程序：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">char</span> * pBuffer = <span class="built_in">malloc</span>(<span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">char</span>));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* Do some work */</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面代码是非常常见的内存泄漏场景(也可以使用new来进行分配)，我们申请了一块内存，但是在fun函数结束时候没有调用free函数进行内存释放。</p>
<p>在C++开发中，还有一种内存泄漏，如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Obj</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">   <span class="built_in">Obj</span>(<span class="keyword">int</span> size) &#123;</span><br><span class="line">     buffer_ = <span class="keyword">new</span> <span class="keyword">char</span>;</span><br><span class="line">   &#125;</span><br><span class="line">   ~<span class="built_in">Obj</span>()&#123;&#125;</span><br><span class="line">  <span class="keyword">private</span>:</span><br><span class="line">   <span class="keyword">char</span> *buffer_;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Object obj;</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面这段代码中，析构函数没有释放成员变量buffer_指向的内存，所以<strong>在编写析构函数的时候，一定要仔细分析成员变量有没有申请动态内存，如果有，则需要手动释放，我们重新编写了析构函数</strong>，如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">~<span class="built_in">Object</span>() &#123;</span><br><span class="line">  <span class="keyword">delete</span> buffer_;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>在C/C++中，对于普通函数，如果申请了堆资源，请根据代码的具体场景调用free/delete进行资源释放；对于class，如果申请了堆资源，则需要在对应的析构函数中调用free/delete进行资源释放。</strong></p>
<hr>
<h3 id="未分配"><a href="#未分配" class="headerlink" title="未分配"></a>未分配</h3><p>在C++中，我们经常使用new操作符来进行内存分配，其内部主要做了两件事：</p>
<ol>
<li>通过operator new从堆上申请内存(glibc下，operator new底层调用的是malloc)</li>
<li>调用构造函数(如果操作对象是一个class的话)</li>
</ol>
<p>对应的，使用delete操作符来释放内存，其顺序正好与new相反：</p>
<ol>
<li>调用对象的析构函数(如果操作对象是一个class的话)</li>
<li>通过operator delete释放内存</li>
</ol>
<p>比如以下代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="keyword">operator</span> <span class="title">new</span><span class="params">(std::<span class="keyword">size_t</span> size)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">void</span>* p = <span class="built_in">malloc</span>(size);</span><br><span class="line">    <span class="keyword">if</span> (p == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span>(<span class="string">&quot;new failed to allocate %zu bytes&quot;</span>, size);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span>[](std::<span class="keyword">size_t</span> size) &#123;</span><br><span class="line">    <span class="keyword">void</span>* p = <span class="built_in">malloc</span>(size);</span><br><span class="line">    <span class="keyword">if</span> (p == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span>(<span class="string">&quot;new[] failed to allocate %zu bytes&quot;</span>, size);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span>  <span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="keyword">void</span>* ptr)</span> <span class="title">throw</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">free</span>(ptr);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span>  <span class="keyword">operator</span> <span class="keyword">delete</span>[](<span class="keyword">void</span>* ptr) <span class="keyword">throw</span>() &#123;</span><br><span class="line">    <span class="built_in">free</span>(ptr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为了加深对于new和delete的内部逻辑的理解，我们再举一个例子：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Test</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">   <span class="built_in">Test</span>() &#123;</span><br><span class="line">     std::cout &lt;&lt; <span class="string">&quot;in Test&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="comment">// other</span></span><br><span class="line">   ~<span class="built_in">Test</span>() &#123;</span><br><span class="line">     std::cout &lt;&lt; <span class="string">&quot;in ~Test&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Test *t = <span class="keyword">new</span> Test;</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  <span class="keyword">delete</span> t;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述main函数中，我们使用new 操作符创建一个Test类指针</p>
<ol>
<li>通过operator new申请内存(底层malloc实现)</li>
<li>通过placement new在上述申请的内存块上调用构造函数</li>
<li>调用ptr-&gt;~Test()释放Test对象的成员变量</li>
<li>调用operator delete释放内存</li>
</ol>
<p>上述过程，可以理解为如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// new</span></span><br><span class="line"><span class="keyword">void</span> *ptr = <span class="built_in">malloc</span>(<span class="built_in"><span class="keyword">sizeof</span></span>(Test));</span><br><span class="line">t = <span class="built_in"><span class="keyword">new</span></span>(ptr)Test</span><br><span class="line">  </span><br><span class="line"><span class="comment">// delete</span></span><br><span class="line">ptr-&gt;~<span class="built_in">Test</span>();</span><br><span class="line"><span class="built_in">free</span>(ptr);</span><br></pre></td></tr></table></figure>

<hr>
<p>上面我们讲解了C++中new和delete操作符的基本实现和内部逻辑，在清楚了这些后，我们来分析一下产生内存泄漏的几种类型。</p>
<hr>
<h4 id="new-和-free"><a href="#new-和-free" class="headerlink" title="new 和 free"></a>new 和 free</h4><p>仍然以上面的Test对象为例，代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Test *t = <span class="keyword">new</span> Test;</span><br><span class="line"><span class="built_in">free</span>(t)</span><br></pre></td></tr></table></figure>

<p>此处会产生内存泄漏，在上面，我们已经分析过，new操作符会先通过operator new分配一块内存，然后在该块内存上调用placement new即调用Test的构造函数。而在上述代码中，只是通过free函数释放了内存，但是没有调用Test的析构函数以释放Test的成员变量，从而引起内存泄漏。</p>
<p>正确的做法是：<strong>malloc和free一一对应，new和delete一一对应。</strong></p>
<h4 id="new-和-delete"><a href="#new-和-delete" class="headerlink" title="new[] 和 delete"></a>new[] 和 delete</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Test *t = <span class="keyword">new</span> Test [<span class="number">10</span>];</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  <span class="keyword">delete</span> t;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述代码中，我们通过new创建了一个Test类型的数组，然后通delete操作符删除该数组，编译并执行，输出如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in ~Test</span><br></pre></td></tr></table></figure>

<p>从上面输出结果可以看出，调用了10次构造函数，但是只调用了一次析构函数，所以引起了内存泄漏。这是因为调用delete t释放了通过operator new[]申请的内存，即malloc申请的内存块，且只调用了t[0]对象的析构函数，t[1..9]对象的析构函数并没有被调用。</p>
<p> <strong>delete  ptr  代表用来释放内存，且只用来释放ptr指向的内存，如果ptr指向的是一个对象数组，则只会调用数组中第一个对象的destructor。</strong> </p>
<p> <strong>delete[]  ptr  用来释放ptr指向的内存，还逐一调用数组中每个对象的destructor。</strong></p>
<p> <strong>当然，对于像int/char/long/int*/struct等等简单数据类型，由于对象没有destructor，所以用delete 和delete [] 是一样的，但是如果是C++对象数组就不同了。</strong></p>
<p>正确的做法应该是<code>delete [] t;</code></p>
<hr>
<h4 id="虚析构"><a href="#虚析构" class="headerlink" title="虚析构"></a>虚析构</h4><p>如果基类的析构函数不声明为virtual，基类指针指向子类对象，析构时就不会调用子类的析构函数，可能造成内存泄漏。看下面这个例子：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Base</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Base</span>()&#123;</span><br><span class="line">    buffer_ = <span class="keyword">new</span> <span class="keyword">char</span>[<span class="number">10</span>];</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ~<span class="built_in">Base</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in Base::~Base&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">delete</span> []buffer_;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">char</span> *buffer_;</span><br><span class="line"></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> :</span> <span class="keyword">public</span> Base &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Derived</span>()&#123;&#125;</span><br><span class="line"></span><br><span class="line">  ~<span class="built_in">Derived</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in Derived::~Derived&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Base *base = <span class="keyword">new</span> Derived;</span><br><span class="line">  <span class="keyword">delete</span> base;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面代码输出如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">in Base::~Base</span><br></pre></td></tr></table></figure>

<p>可见，上述代码并没有调用派生类Derived的析构函数，如果派生类中在堆上申请了资源，那么就会产生内存泄漏。</p>
<p><strong>为了避免因为继承导致的内存泄漏，我们需要将父类的析构函数声明为virtual</strong>，代码如下(只列了部分修改代码，其他不变):</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">Base</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in Base::~Base&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">delete</span> []buffer_;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>然后重新执行代码，输出结果如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">in Derived::~Derived</span><br><span class="line">in Base::~Base</span><br></pre></td></tr></table></figure>

<p>另外，我们复习一下在继承情况下，构造函数和析构函数的调用顺序。</p>
<p>派生类对象在创建时构造函数调用顺序：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 调用父类的构造函数</span><br><span class="line">2. 调用父类成员变量的构造函数</span><br><span class="line">3. 调用派生类本身的构造函数</span><br></pre></td></tr></table></figure>

<p>派生类对象在析构时的析构函数调用顺序：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 执行派生类自身的析构函数</span><br><span class="line">2. 执行派生类成员变量的析构函数</span><br><span class="line">3. 执行父类的析构函数</span><br></pre></td></tr></table></figure>

<p>为了避免存在继承关系时候的内存泄漏，请遵守一条规则：<strong>无论派生类有没有申请堆上的资源，请将父类的析构函数声明为virtual。</strong></p>
<hr>
<h4 id="循环引用"><a href="#循环引用" class="headerlink" title="循环引用"></a>循环引用</h4><p>在C++开发中，为了尽可能的避免内存泄漏，自C++11起引入了智能指针<code>smart pointer</code>，常见的有shared_ptr、weak_ptr以及unique_ptr等(auto_ptr已经被废弃)，其中weak_ptr是为了解决循环引用而存在，其往往与shared_ptr结合使用。</p>
<p>下面，我们看一段代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Controller</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Controller</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">  ~<span class="built_in">Controller</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in ~Controller&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">SubController</span> &#123;</span></span><br><span class="line">   <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">SubController</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">SubController</span>() &#123;</span><br><span class="line">      std::cout &lt;&lt; <span class="string">&quot;in ~SubController&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    std::shared_ptr&lt;Controller&gt; controller_;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::shared_ptr&lt;SubController&gt; sub_controller_;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">auto</span> controller = std::make_shared&lt;Controller&gt;();</span><br><span class="line">  <span class="keyword">auto</span> sub_controller = std::make_shared&lt;Controller::SubController&gt;();</span><br><span class="line"></span><br><span class="line">  controller-&gt;sub_controller_ = sub_controller;</span><br><span class="line">  sub_controller-&gt;controller_ = controller;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编译并执行上述代码，发现并没有调用Controller和SubController的析构函数，我们尝试着打印下引用计数，代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">auto</span> controller = std::make_shared&lt;Controller&gt;();</span><br><span class="line">  <span class="keyword">auto</span> sub_controller = std::make_shared&lt;Controller::SubController&gt;();</span><br><span class="line"></span><br><span class="line">  controller-&gt;sub_controller_ = sub_controller;</span><br><span class="line">  sub_controller-&gt;controller_ = controller;</span><br><span class="line"></span><br><span class="line">  std::cout &lt;&lt; <span class="string">&quot;controller use_count: &quot;</span> &lt;&lt; controller.<span class="built_in">use_count</span>() &lt;&lt; std::endl;</span><br><span class="line">  std::cout &lt;&lt; <span class="string">&quot;sub_controller use_count: &quot;</span> &lt;&lt; sub_controller.<span class="built_in">use_count</span>() &lt;&lt; std::endl;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编译并执行之后，输出如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">controller use_count: 2</span><br><span class="line">sub_controller use_count: 2</span><br></pre></td></tr></table></figure>

<p>通过上面输出可以发现，因为引用计数都是2，所以在main函数结束的时候，不会调用controller和sub_controller的析构函数，所以就出现了<strong>内存泄漏</strong>。</p>
<p>上面产生内存泄漏的原因，就是我们常说的<strong>循环引用</strong>。</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/4.png" alt="4" style="zoom:50%;">

<p>为了解决std::shared_ptr循环引用导致的内存泄漏，我们可以使用std::weak_ptr来单面去除上图中的循环：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Controller</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Controller</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">  ~<span class="built_in">Controller</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in ~Controller&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">SubController</span> &#123;</span></span><br><span class="line">   <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">SubController</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">SubController</span>() &#123;</span><br><span class="line">      std::cout &lt;&lt; <span class="string">&quot;in ~SubController&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    std::weak_ptr&lt;Controller&gt; controller_;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::shared_ptr&lt;SubController&gt; sub_controller_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>在上述代码中，我们将SubController类中controller_的类型从std::shared_ptr变成std::weak_ptr，重新编译执行，结果如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">controller use_count: 1</span><br><span class="line">sub_controller use_count: 2</span><br><span class="line">in ~Controller</span><br><span class="line">in ~SubController</span><br></pre></td></tr></table></figure>

<p>从上面结果可以看出，controller和sub_controller均以释放，所以<code>循环引用</code>引起的内存泄漏问题，也得以解决。</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/5.png" alt="5" style="zoom:50%;">

<p>可能有人会问，使用std::shared_ptr可以直接访问对应的成员函数，如果是std::weak_ptr的话，怎么访问呢？我们可以使用下面的方式:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">std::shared_ptr controller = controller_.<span class="built_in">lock</span>();</span><br></pre></td></tr></table></figure>

<p>即在子类SubController中，如果要使用controller调用其对应的函数，就可以使用上面的方式。</p>
<hr>
<h2 id="避免内存泄漏"><a href="#避免内存泄漏" class="headerlink" title="避免内存泄漏"></a>避免内存泄漏</h2><p>针对不同行为产生的内存泄漏有不同的避免方法，主要有以下几种方法：</p>
<h4 id="避免在堆上分配"><a href="#避免在堆上分配" class="headerlink" title="避免在堆上分配"></a>避免在堆上分配</h4><p>众所周知，大部分的内存泄漏都是因为在堆上分配引起的，如果我们不在堆上进行分配，就不会存在内存泄漏了(这不废话嘛)，我们可以根据具体的使用场景，如果对象可以在栈上进行分配，就在栈上进行分配，一方面栈的效率远高于堆，另一方面，还能避免内存泄漏，我们何乐而不为呢。</p>
<h4 id="手动释放"><a href="#手动释放" class="headerlink" title="手动释放"></a>手动释放</h4><ul>
<li>对于malloc函数分配的内存，在结束使用的时候，使用free函数进行释放。</li>
<li>对于new操作符创建的对象，切记使用delete来进行释放。</li>
<li>对于new []创建的对象，使用delete[]来进行释放(使用free或者delete均会造成内存泄漏)。</li>
</ul>
<h4 id="避免使用裸指针"><a href="#避免使用裸指针" class="headerlink" title="避免使用裸指针"></a>避免使用裸指针</h4><p>尽可能避免使用裸指针，除非所调用的lib库或者合作部门的接口是裸指针。</p>
<p>比如如下代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">(<span class="keyword">int</span> *ptr)</span> </span>&#123;<span class="comment">// fun 是一个接口或lib函数</span></span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;&#125;</span><br><span class="line">  <span class="keyword">int</span> a = <span class="number">1000</span>;</span><br><span class="line">  <span class="keyword">int</span> *ptr = &amp;a;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="built_in">fun</span>(ptr);</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的fun函数中，有一个参数ptr,为int *，我们需要根据上下文来分析这个指针是否需要释放，这是一种很不好的设计。</p>
<h4 id="使用STL中或者自己实现对象"><a href="#使用STL中或者自己实现对象" class="headerlink" title="使用STL中或者自己实现对象"></a>使用STL中或者自己实现对象</h4><p>在C++中，提供了相对完善且可靠的STL供我们使用，所以能用STL的尽可能的避免使用C中的编程方式，比如：</p>
<ul>
<li>使用std::string 替代char *, string类自己会进行内存管理，而且优化的相当不错。</li>
<li>使用std::vector或者std::array来替代传统的数组。</li>
<li>其它适合使用场景的对象。</li>
</ul>
<h4 id="智能指针"><a href="#智能指针" class="headerlink" title="智能指针"></a>智能指针</h4><p>自C++11开始，STL中引入了智能指针(smart pointer)来动态管理资源，针对使用场景的不同，提供了以下三种智能指针。</p>
<p><strong>unique_ptr</strong></p>
<p>unique_ptr是限制最严格的一种智能指针，用来替代之前的auto_ptr，<strong>独享被管理对象指针所有权。当unique_ptr对象被销毁时，会在其析构函数内删除关联的原始指针。</strong></p>
<p>unique_ptr对象分为以下两类：</p>
<ul>
<li><p>unique_ptr该类型的对象关联了单个Type类型的指针。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::unique_ptr&lt;Type&gt;   <span class="title">p1</span><span class="params">(<span class="keyword">new</span> Type)</span></span>; <span class="comment">// c++11</span></span><br><span class="line"><span class="keyword">auto</span> p1 = std::make_unique&lt;Type&gt;(); <span class="comment">// c++14</span></span><br></pre></td></tr></table></figure></li>
<li><p>unique_ptr&lt;Type[]&gt; 该类型的对象关联了多个Type类型指针，即一个对象数组</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::unique_ptr&lt;Type[]&gt; <span class="title">p2</span><span class="params">(<span class="keyword">new</span> Type[n]())</span></span>; <span class="comment">// c++11</span></span><br><span class="line"><span class="keyword">auto</span> p2 = std::make_unique&lt;Type[]&gt;(n); <span class="comment">// c++14</span></span><br></pre></td></tr></table></figure></li>
<li><p>不可用被复制</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">unique_ptr&lt;<span class="keyword">int</span>&gt; <span class="title">a</span><span class="params">(<span class="keyword">new</span> <span class="keyword">int</span>(<span class="number">0</span>))</span></span>;</span><br><span class="line">unique_ptr&lt;<span class="keyword">int</span>&gt; b = a;  <span class="comment">// 编译错误</span></span><br><span class="line">unique_ptr&lt;<span class="keyword">int</span>&gt; b = std::<span class="built_in">move</span>(a); <span class="comment">// 可以通过move语义进行所有权转移</span></span><br></pre></td></tr></table></figure></li>
</ul>
<p>根据使用场景，可以使用std::unique_ptr来避免内存泄漏，如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="function">unique_ptr&lt;<span class="keyword">int</span>&gt; <span class="title">a</span><span class="params">(<span class="keyword">new</span> <span class="keyword">int</span>(<span class="number">0</span>))</span></span>;</span><br><span class="line">  <span class="comment">// use a</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述fun函数结束的时候，会<strong>自动调用a的析构函数，从而释放其关联的指针。</strong></p>
<p><strong>shared_ptr</strong></p>
<p>与unique_ptr不同的是，unique_ptr是<strong>独占管理权</strong>，而shared_ptr则是<strong>共享管理权</strong>，即多个shared_ptr可以共用同一块关联对象，其内部采用的是<strong>引用计数</strong>，在拷贝的时候，引用计数+1，而在某个对象退出作用域或者释放的时候，引用计数-1，当<strong>引用计数为0的时候，会自动释放其管理的对象。</strong></p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="built_in">std</span>::<span class="built_in">shared_ptr</span>&lt;Type&gt; a; <span class="comment">// a是一个空对象</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">shared_ptr</span>&lt;Type&gt; b = <span class="built_in">std</span>::make_shared&lt;Type&gt;(); <span class="comment">// 分配资源</span></span><br><span class="line">    a = b; <span class="comment">// 此时引用计数为2</span></span><br><span class="line">    &#123;</span><br><span class="line">      <span class="built_in">std</span>::<span class="built_in">shared_ptr</span>&lt;Type&gt; c = a; <span class="comment">// 此时引用计数为3</span></span><br><span class="line">    &#125; <span class="comment">// c退出作用域，此时引用计数为2</span></span><br><span class="line">  &#125; <span class="comment">// b 退出作用域，此时引用计数为1</span></span><br><span class="line">&#125; <span class="comment">// a 退出作用域，引用计数为0，释放对象</span></span><br></pre></td></tr></table></figure>

<p><strong>weak_ptr</strong></p>
<p>weak_ptr的出现，主要是为了解决shared_ptr的<strong>循环引用</strong>，其主要是与shared_ptr一起来使用。和shared_ptr不同的地方在于，其并不会拥有资源，也就是说不能访问对象所提供的成员函数，不过，<strong>可以通过weak_ptr.lock()来产生一个拥有访问权限的shared_ptr。</strong></p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">std::weak_ptr&lt;Type&gt; a;</span><br><span class="line">&#123;</span><br><span class="line">  std::shared_ptr&lt;Type&gt; b = std::make_shared&lt;Type&gt;();</span><br><span class="line">  a = b</span><br><span class="line">&#125; <span class="comment">// b所对应的资源释放</span></span><br></pre></td></tr></table></figure>

<h4 id="RAII"><a href="#RAII" class="headerlink" title="RAII"></a>RAII</h4><p><code>RAII</code>是<code>Resource Acquisition is Initialization(资源获取即初始化)</code>的缩写，是C++语言的一种管理资源，避免泄漏的用法。</p>
<p>利用的就是<strong>C++构造的对象最终会被销毁的原则</strong>。利用C++对象生命周期的概念来控制程序的资源,比如内存,文件句柄,网络连接等。</p>
<p><strong>RAII的做法是使用一个对象，在其构造时获取对应的资源，在对象生命周期内控制对资源的访问，使之始终保持有效，最后在对象析构的时候，释放构造时获取的资源。</strong></p>
<p><strong>简单地说，就是把资源的使用限制在对象的生命周期之中，自动释放。</strong></p>
<p>举个简单的例子，通常在多线程编程的时候，都会用到std::mutex，如下代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">std::mutex mutex_;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  mutex_.<span class="built_in">lock</span>();</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (...) &#123;</span><br><span class="line">    mutex_.<span class="built_in">unlock</span>();</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  mutex_.<span class="built_in">unlock</span>()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述代码中，如果if分支多的话，每个if分支里面都要释放锁，如果一不小心忘记释放，那么就会造成故障，为了解决这个问题，我们使用<code>RAII</code>技术，代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">std::mutex mutex_;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="function">std::lock_guard&lt;std::mutex&gt; <span class="title">guard</span><span class="params">(mutex_)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (...) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注：std::lock_guard属于C++11特性，锁管理遵循RAII习语管理资源，锁管理器在构造函数中自动绑定它的互斥体并加锁，在析构函数中解锁，大大减少了死锁的风险。</p>
</blockquote>
<p>在guard出了fun作用域的时候，会自动调用mutex_.lock()进行释放，避免了很多不必要的问题。</p>
<hr>
<h2 id="内存泄漏的定位"><a href="#内存泄漏的定位" class="headerlink" title="内存泄漏的定位"></a>内存泄漏的定位</h2><p>在发现程序存在内存泄漏后，往往需要定位泄漏点，而定位这一步往往是最困难的，所以经常为了定位泄漏点，采取各种各样的方案，甭管方案优雅与否，只要能够定位到泄漏点就是好的方法。</p>
<h4 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h4><p>这种方案的核心思想，就是<strong>在每次分配内存的时候，打印指针地址，在释放内存的时候，打印内存地址，这样在程序结束的时候，通过分配和释放的差，如果分配的条数大于释放的条数，那么基本就能确定程序存在内存泄漏</strong>，然后根据日志进行详细分析和定位。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">char</span> * <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">char</span> *p = (<span class="keyword">char</span>*)<span class="built_in">malloc</span>(<span class="number">20</span>);</span><br><span class="line">  <span class="built_in">printf</span>(<span class="string">&quot;%s, %d, address is: %p&quot;</span>, __FILE__, __LINE__, p);</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="built_in">fun</span>();</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="统计"><a href="#统计" class="headerlink" title="统计"></a>统计</h4><p>统计方案可以理解为日志方案的一种特殊实现，其主要原理是<strong>在分配的时候，统计分配次数，在释放的时候，则是统计释放的次数</strong>，这样在程序结束前判断这俩值是否一致，就能判断出是否存在内存泄漏。</p>
<p>此方法可帮助跟踪已分配内存的状态。为了实现这个方案，需要创建三个自定义函数，一个用于内存分配，第二个用于内存释放，最后一个用于检查内存泄漏。代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> allocated  = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> deallocated  = <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">Memory_Allocate</span> <span class="params">(<span class="keyword">size_t</span> size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">void</span> *ptr = <span class="literal">NULL</span>;</span><br><span class="line">    ptr = <span class="built_in">malloc</span>(size);</span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">NULL</span> != ptr) &#123;</span><br><span class="line">        ++allocated;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//Log error</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ptr;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Memory_Deallocate</span> <span class="params">(<span class="keyword">void</span> *ptr)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(pvHandle != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="built_in">free</span>(ptr);</span><br><span class="line">        ++deallocated;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">Check_Memory_Leak</span><span class="params">(<span class="keyword">void</span>)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (allocated != deallocated) &#123;</span><br><span class="line">        <span class="comment">//Log error</span></span><br><span class="line">        ret = MEMORY_LEAK;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        ret = OK;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h4><p>在Linux上比较常用的内存泄漏检测工具是<strong>valgrind</strong>，所以咱们就以valgrind为工具，进行检测。</p>
<p>我们首先看一段代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span> <span class="params">(<span class="keyword">void</span>)</span></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> *buff = (<span class="keyword">char</span>*)<span class="built_in">malloc</span>(<span class="number">10</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span> <span class="params">(<span class="keyword">void</span>)</span></span>&#123;</span><br><span class="line">    <span class="built_in">func</span>(); <span class="comment">// 产生内存泄漏</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>通过<code>gcc -g leak.c -o leak</code>命令进行编译</li>
<li>执行<code>valgrind --leak-check=full ./leak</code></li>
</ul>
<p>在上述的命令执行后，会输出如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">==9652== Memcheck, a memory error detector</span><br><span class="line">==9652== Copyright (C) 2002-2017, and GNU GPL&#x27;d, by Julian Seward et al.</span><br><span class="line">==9652== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info</span><br><span class="line">==9652== Command: ./leak</span><br><span class="line">==9652==</span><br><span class="line">==9652==</span><br><span class="line">==9652== HEAP SUMMARY:</span><br><span class="line">==9652==     in use at exit: 10 bytes in 1 blocks</span><br><span class="line">==9652==   total heap usage: 1 allocs, 0 frees, 10 bytes allocated</span><br><span class="line">==9652==</span><br><span class="line">==9652== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1</span><br><span class="line">==9652==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)</span><br><span class="line">==9652==    by 0x40052E: func (leak.c:4)</span><br><span class="line">==9652==    by 0x40053D: main (leak.c:8)</span><br><span class="line">==9652==</span><br><span class="line">==9652== LEAK SUMMARY:</span><br><span class="line">==9652==    definitely lost: 10 bytes in 1 blocks</span><br><span class="line">==9652==    indirectly lost: 0 bytes in 0 blocks</span><br><span class="line">==9652==      possibly lost: 0 bytes in 0 blocks</span><br><span class="line">==9652==    still reachable: 0 bytes in 0 blocks</span><br><span class="line">==9652==         suppressed: 0 bytes in 0 blocks</span><br><span class="line">==9652==</span><br><span class="line">==9652== For lists of detected and suppressed errors, rerun with: -s</span><br><span class="line">==9652== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)</span><br></pre></td></tr></table></figure>

<p>valgrind的检测信息将内存泄漏分为如下几类：</p>
<ul>
<li><strong>definitely lost</strong>：确定产生内存泄漏</li>
<li><strong>indirectly lost</strong>：间接产生内存泄漏</li>
<li><strong>possibly lost</strong>：可能存在内存泄漏</li>
<li><strong>still reachable</strong>：即使在程序结束时候，仍然有指针在指向该块内存，常见于全局变量</li>
</ul>
<p>主要上面输出的下面几句：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">==9652==    by 0x40052E: func (leak.c:4)</span><br><span class="line">==9652==    by 0x40053D: main (leak.c:8)</span><br></pre></td></tr></table></figure>

<p>提示在main函数(leak.c的第8行)fun函数(leak.c的第四行)产生了内存泄漏，通过分析代码，原因定位，问题解决。</p>
<p>valgrind不仅可以检测内存泄漏，还有其他很强大的功能，由于本文以内存泄漏为主，所以其他的功能就不在此赘述了，有兴趣的可以通过<code>valgrind --help</code>来进行查看。</p>
<p>对于Windows下的内存泄漏检测工具，笔者推荐一款轻量级功能却非常强大的工具<strong>UMDH</strong>，如果你在Windows上进行开发，强烈推荐。</p>
<hr>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在C/C++开发过程中，内存泄漏是一个非常常见的问题，其影响相对来说远低于coredump等，所以遇到内存泄漏的时候，不用过于着急，大不了重启嘛。</p>
<p>在开发过程中遵守下面的规则，基本能90+%避免内存泄漏：</p>
<ul>
<li>良好的编程习惯，只有有malloc/new，就得有free/delete。</li>
<li>尽可能的使用智能指针，智能指针就是为了解决内存泄漏而产生。</li>
<li>使用log进行记录。</li>
<li>也是最重要的一点，<strong>谁申请，谁释放</strong>。</li>
</ul>
<p>对于malloc分配内存，分配失败的时候返回值为NULL，此时程序可以直接退出了，而对于new进行内存分配，其分配失败的时候，是抛出<code>std::bad_alloc</code>，所以为了第一时间发现问题，不要对new异常进行catch，毕竟内存都分配失败了，程序也没有运行的必要了。</p>
<p>C/C++之所以复杂、效率高，是因为其灵活性，可用直接访问操作系统API，而正因为其灵活性，就很容易出问题，团队成员必须愿意按照一定的规则来进行开发，有完整的review机制，将问题暴露在上线之前。这样才可以把精力放在业务本身，而不是查找这些问题上，有时候往往一个小问题就能消耗很久的时间去定位解决，所以，一定要有一个良好的开发习惯。</p>
<hr>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a target="_blank" rel="noopener" href="https://developers.redhat.com/blog/2021/05/05/memory-error-checking-in-c-and-c-comparing-sanitizers-and-valgrind">https://developers.redhat.com/blog/2021/05/05/memory-error-checking-in-c-and-c-comparing-sanitizers-and-valgrind</a></p>
<p><a target="_blank" rel="noopener" href="https://aticleworld.com/what-is-memory-leak-in-c-c-how-can-we-avoid/">https://aticleworld.com/what-is-memory-leak-in-c-c-how-can-we-avoid/</a></p>
<p><a target="_blank" rel="noopener" href="https://iq.opengenus.org/memory-leak-in-cpp-and-how-to-avoid-it/">https://iq.opengenus.org/memory-leak-in-cpp-and-how-to-avoid-it/</a></p>
<p><a target="_blank" rel="noopener" href="https://blog.nelhage.com/post/three-kinds-of-leaks/#type-1-unreachable-allocations">https://blog.nelhage.com/post/three-kinds-of-leaks/#type-1-unreachable-allocations</a></p>
<p><a target="_blank" rel="noopener" href="https://owasp.org/www-community/vulnerabilities/Memory_leak">https://owasp.org/www-community/vulnerabilities/Memory_leak</a></p>
<p><a target="_blank" rel="noopener" href="https://www.usna.edu/Users/cs/roche/courses/s19ic221/lab05.html">https://www.usna.edu/Users/cs/roche/courses/s19ic221/lab05.html</a></p>
<p><a target="_blank" rel="noopener" href="https://stackoverflow.com/questions/6261201/how-to-find-memory-leak-in-a-c-code-project">https://stackoverflow.com/questions/6261201/how-to-find-memory-leak-in-a-c-code-project</a></p>
<p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/pbNJA_qNZc3KpvoGgHXEIw">https://mp.weixin.qq.com/s/pbNJA_qNZc3KpvoGgHXEIw</a></p>
</div></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">Author: </span><span class="post-copyright-info"><a href="mailto:undefined">LiYang</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">Link: </span><span class="post-copyright-info"><a href="http://example.com/2022/05/13/C-中的内存泄漏/">http://example.com/2022/05/13/C-中的内存泄漏/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">Copyright Notice: </span><span class="post-copyright-info">All articles in this blog are licensed under <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> unless stating additionally.</span></div></div><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/C/">C++</a></div><nav id="pagination"><div class="prev-post pull-left"><a href="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/"><i class="fa fa-chevron-left">  </i><span>如何理解“进入内核态”?</span></a></div><div class="next-post pull-right"><a href="/2022/04/18/Learn-Git-Branching/"><span>Learn_Git_Branching</span><i class="fa fa-chevron-right"></i></a></div></nav></div></div><footer><div class="layout" id="footer"><div class="copyright">&copy;2013 - 2022 By LiYang</div><div class="framework-info"><span>Driven - </span><a target="_blank" rel="noopener" href="http://hexo.io"><span>Hexo</span></a><span class="footer-separator">|</span><span>Theme - </span><a target="_blank" rel="noopener" href="https://github.com/Molunerfinn/hexo-theme-melody"><span>Melody</span></a></div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_page_pv"><i class="fa fa-file"></i><span id="busuanzi_value_page_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/js/utils.js?version=1.9.0"></script><script src="/js/fancybox.js?version=1.9.0"></script><script src="/js/sidebar.js?version=1.9.0"></script><script src="/js/copy.js?version=1.9.0"></script><script src="/js/fireworks.js?version=1.9.0"></script><script src="/js/transition.js?version=1.9.0"></script><script src="/js/scroll.js?version=1.9.0"></script><script src="/js/head.js?version=1.9.0"></script><script>if(/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
  $('#top-container').addClass('is-mobile')
}</script></body></html>